home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 011 / extend.arc / EXTEND.PAS
Pascal/Delphi Source File  |  1987-03-22  |  24KB  |  447 lines

  1. (******************************************************************************
  2.  
  3.                                   EXTEND.PAS
  4.                                   Version 1.5
  5.                                 August 25, 1986
  6.                                by Randy Forgaard
  7.                              CompuServe 70307,521
  8.  
  9.  
  10. MS-DOS and PC-DOS Turbo Pascal 3.0 and higher only allow up to 15 files to be
  11. open at the same time, due to limitations in DOS.  This file shows you how to
  12. have up to 96 files open simultaneously under DOS 2.0 or 2.1, or 252 files open
  13. simultaneously under DOS 3.0 or higher.  Below is a description of how to use
  14. this technique, followed by a technical explanation of the implementation, for
  15. those who are interested.
  16.  
  17.  
  18. TO USE THIS TECHNIQUE:
  19.  
  20. You need the routines and global declarations below.  Everywhere in your
  21. program that you Reset or Rewrite a file "f" for the first time, insert an
  22. "OpenExtend(f);" invocation immediately after the Reset or Rewrite.  At every
  23. place in your program that you call one of Turbo's built-in routines (other
  24. than Assign) for handling files (e.g., Read, Write, Close, Seek, Reset,
  25. Rewrite, etc.), put an "UnExtend(f);" invocation immediately prior to the call,
  26. and a "ReExtend(f);" invocation immediately after the call.  Do not, however,
  27. insert UnExtend and ReExtend calls around the very first Reset or Rewrite that
  28. you use to initially open a file.
  29.  
  30. At the very top of your program, prior to the "program" statement, put the
  31. compiler directive {$F252}.  (You may use a value smaller than 252, if you
  32. wish.  Under DOS 2.0/2.1, values above 96 have no additional benefit.  Each
  33. larger value for the {$Fnnn} directive uses 2 additional bytes in the program's
  34. global data space.)  The value you specify for the {$Fnnn} directive is the
  35. maximum number of files you will be able to have open at the same time in your
  36. program.
  37.  
  38. Edit your CONFIG.SYS file (see the DOS manual for details), so that it includes
  39. a line that says "FILES=nnn".  The value of "nnn" should be 3 greater than the
  40. value you specified for the {$Fnnn} directive (larger values will provide no
  41. additional benefit, with respect to your individual program), and should not
  42. exceed 99 (for DOS 2.0/2.1) or 255 (for DOS 3.0 and higher).  Under any version
  43. of DOS, the minimum allowable value for "FILES=nnn" is 8.  Then, reboot your
  44. computer so that the "FILES=nnn" parameter takes hold.  That's it!
  45.  
  46. Note: EXTEND.PAS uses a typed constant whose value changes during the course of
  47. execution.  Consequently, when compiling any program that uses EXTEND.PAS you
  48. must either: 1) compile the program in memory and run it only in memory, or 2)
  49. compile the program to a .COM file, and execute the .COM file.  The program
  50. will bomb if you compile and run the program in memory, and then immediately
  51. compile to a .COM file without first leaving and restarting Turbo.
  52.  
  53. Running the sample program below will tell you the maximum number of files you
  54. can have open at once (usually 96 or 252, unless you have chosen a smaller
  55. value for the {$Fnnn} directive, or "FILES=nnn" has a value less than the
  56. maximum, or a resident program has files open, or there are some orphaned files
  57. due to previous program error).  You will need to remove a "comment" line to
  58. run the program...see the instructions below.  This program takes a while to
  59. run, due to the heavy disk I/O, so running it on a hard disk (or, even better,
  60. a RAM disk) is recommended.  Make sure that you are running the program in a
  61. subdirectory, so that you don't run up against the DOS limit on the number of
  62. allowable files in the root directory of a drive.  Enjoy!
  63.  
  64. Note: Turbo/DOS normally closes all open files when a program aborts due to a
  65. run-time error or I/O error (though some data in Turbo Text output files will
  66. normally be lost in such cases, anyway).  The "Extend" technique presented here
  67. defeats this automatic-close mechanism.  This can leave lost clusters on the
  68. disk, and can cause the DCB Table to fill up (see below), if a run-time error
  69. or I/O error occurs while "extended" files are open.  These are not serious
  70. conditions, though you may have to reboot the computer if a subsequent program
  71. aborts with not enough available file handles.  Also, the lost sectors should
  72. be occasionally excised using "CHKDSK /F".  To avoid these two problems,
  73. implement a user-written error handler that will capture all run-time errors
  74. and I/O errors, and allow a graceful exit by closing all of the "extended"
  75. files before exiting to DOS.  For details on writing an error handler, see the
  76. Turbo manual or the README file on the Turbo distribution disk.
  77.  
  78.  
  79. THE TECHNICAL DETAILS:
  80.  
  81. Much of the following information is not documented in the DOS Technical
  82. Reference manual.
  83.  
  84. Under DOS 1.0 and 1.1, all files were accessed via File Control Blocks (FCB's).
  85. There was no limit to the number of FCB's that a program could use, so there
  86. was no limit to the number of files open simultaneously.
  87.  
  88. Under DOS 2.0 and higher, an alternate (and preferable) method of accessing
  89. files was introduced, using a 2-byte integer called a "handle" to refer to a
  90. file.  A "handle" file is described using a data structure called a Device
  91. Control Block (DCB).  However, DOS provides the storage space for all DCB's,
  92. rather than having the application program store the DCB's, so the number of
  93. available DCB's is limited to the amount of space that DOS has set aside for
  94. them.  The maximum number of files/devices that can be open simultaneously, on
  95. the whole computer, is the number of slots available in the DCB Table created
  96. by DOS.  The DCB's in the DCB Table are consecutively numbered, starting with
  97. 0.  DCB's 0, 1, and 2 are predefined by DOS to correspond to the AUX, CON, and
  98. PRN devices, respectively.  All remaining DCB's in the DCB Table are available
  99. for files or devices used by application programs.
  100.  
  101. So that I/O redirection can be supported, the DCB numbers are not used directly
  102. when accessing files.  Instead, a file "handle" is used.  A "handle" is an
  103. index into a 20-byte array, called the Handle Table, located at offset 18H of
  104. the Program Segment Prefix (PSP) for a program.  (For a general discussion of
  105. the PSP, see the DOS Technical Reference manual).  Each element of the Handle
  106. Table is the DCB number of a file or device.  The value at index "handle" in
  107. the Handle Table is the DCB number of the file or device that implements that
  108. file handle.  The handles are numbered from 0 to 19.  Thus, if the value 8 is
  109. in the 6th byte of the Handle Table, the handle "5" refers to the file (or
  110. device) described by the DCB in slot 8 of the DCB Table (remember that handles
  111. are numbered starting at zero).  If a handle is not currently being used, its
  112. entry in the Handle Table is FFH.  DOS predefines the first 5 handles to be
  113. Stdin, Stdout, Stderr, Stdaux, and Stdprn, so the first 5 entries in the Handle
  114. Table are 1, 1, 1, 0, and 2, corresponding to the DCB numbers for the CON (1),
  115. AUX (0), and PRN (2) devices.  This leaves only 15 available handles for
  116. opening files (or new devices).
  117.  
  118. Every time a new handle file is opened, a new handle gets used, and the handle
  119. is freed when the handle file is closed.  Since there are only 20 slots
  120. available in the Handle Table for a program, DOS only allows a "process" to
  121. have a maximum of 20 file handles in use simultaneously (and the first 5
  122. entries are predefined, as just noted, unless those handles get closed and
  123. reused).  Every new handle file requires a unique handle, so only 20
  124. files/devices can be open at the same time by a single process (unless FCB's
  125. are used).  (A "process" is any program that has been loaded into memory by
  126. DOS.  There may be several processes in memory at once, if a multitasker is
  127. being used, or if one program invokes a child program, etc.)  There can be many
  128. more than 20 DCB's in the DCB Table, so the real limitation, for each process,
  129. is the size of the Handle Table in the program's PSP.
  130.  
  131. The size of the DCB Table (i.e., the maximum number of files/devices that can
  132. be open simultaneously in the whole computer) is controlled by the "FILES=nnn"
  133. entry in the CONFIG.SYS file.  The minimum number of slots is 8.  Under DOS
  134. 2.0/2.1, the maximum number is 99, and under DOS 3.0 and higher, the maximum is
  135. 255.  As previously mentioned, the first three of these DCB slots are occupied
  136. by the AUX, CON, and PRN devices.
  137.  
  138. A single program can use all of the DCB's in the DCB Table (except for the 3
  139. reserved by DOS) all on its own, by effectively bypassing the Handle Table in
  140. the PSP, except on a temporary basis.  Instead of allowing DOS to store the DCB
  141. numbers in the Handle Table, the program can store these numbers elsewhere.
  142. Then, to manipulate a file using DOS, the program can temporarily put the DCB
  143. number of that file into a slot in the Handle Table, pass the index of that
  144. table slot (i.e., that "handle") to DOS, and DOS will operate on that
  145. handle/DCB number.  After the DOS call, the program can remove that DCB number
  146. from the designated Handle Table slot, freeing up that handle for use in
  147. another DOS call for another file.  In this way, DOS can be fooled into
  148. accessing up to 96 (or 252) different files/devices using a single handle entry
  149. in the Handle Table, because the only limit is the size of the DCB Table.
  150.  
  151. The Handle Table slot that one decides to use, in the above scheme, might
  152. already be in use.  I.e., the slot might contain a value other than FFH,
  153. indicating that it holds the actual DCB number of another open file.  One can
  154. alleviate the conflict by saving that DCB number in a temporary variable before
  155. temporarily using that Handle Table slot to access DOS, then restore the DCB
  156. number to the slot afterward.
  157.  
  158. The scheme outlined above, for keeping more than 20 files open at the same time
  159. in a single program, will work fine in an assembly language program.  However,
  160. there is one remaining complication when using the technique with Turbo Pascal.
  161. Turbo maintains its own, internal list of all open handles at run-time, which
  162. we will call the Open Handle List.  Turbo adds a handle to the list whenever a
  163. program opens a file using Reset or Rewrite, and then Turbo removes the handle
  164. from the list whenever a program does a Close.
  165.  
  166. Turbo maintains the Open Handle List for the following reason: if a Turbo
  167. program terminates normally to DOS, DOS will automatically close all open
  168. handles.  (Don't depend on this feature, though, for Text output files.  Turbo
  169. maintains a Text buffer in RAM whose contents will be lost if the Text file is
  170. not closed using Turbo's Close routine.)  However, if one invokes a Turbo
  171. program "in memory," directly from Turbo's main menu, DOS is not able to close
  172. the open handles upon termination (because Turbo itself is still running, so
  173. DOS doesn't know that the "in memory" Turbo program terminated).  For this
  174. reason, Turbo takes over the duty of closing all open handles when a Turbo
  175. program terminates.  To do so, Turbo maintains the Open Handle List.
  176.  
  177. The maximum size of the Open Handle List is set by the rarely-used {$Fnnn}
  178. compiler directive that Turbo provides.  Thus, {$F20} sets aside space for 20
  179. open handles on the list.  If you attempt to open a file in a normal Turbo
  180. program that does not use the "Extend" technique outlined in this document, and
  181. you get an I/O error F3 ("Too many open files" -- not documented in early
  182. editions of the Turbo 3.0 manual), it can mean either: (1) there are no more
  183. available handles in the Handle Table of the PSP; (2) there are no more
  184. available DCB's in the global DCB Table; or (3) Turbo's Open Handle List is
  185. full.  In practice, I/O error F3 usually means (1), because most people have
  186. (or should have) at least FILES=20 in their CONFIG.SYS file (so the DCB Table
  187. should still have some available entries), and the default value for the
  188. {$Fnnn} directive is 16 (which is greater than the 15 handles that are normally
  189. available in the Handle Table).
  190.  
  191. The Open Handle List injects the following complication into the "Extend"
  192. scheme presented above: When Reset or Rewrite is used to open a Turbo file,
  193. Turbo adds that handle to the Open Handle List, as noted above.  We then free
  194. up that Handle Table slot by copying the DCB number in that slot to another
  195. location, and store FFH into that slot.  Now, when we want to access the file,
  196. we are free to temporarily put that DCB number into any Handle Table slot,
  197. perform the file access, and then restore any previous DCB number that may have
  198. been in that slot.  However, when we Close the file, we must be sure to use
  199. exactly the same Handle Table slot as was used when the file was opened.  This
  200. is because, when we execute the Turbo Close routine, Close must "see" the same
  201. handle as was used to open the file, so that it can successfully remove that
  202. handle from Open Handle List.  If we do not use the same Handle Table slot when
  203. we Close the file, the original handle will not get removed from the Open
  204. Handle List, and the Open Handle List will start to fill up.  Eventually, if
  205. files keep getting opened and closed, a Rewrite or Reset will result in an I/O
  206. error F3 (because the Open Handle List is full), even though there may be
  207. available slots in both the Handle Table and the DCB Table.
  208.  
  209. In the "Extend" technique proposed here, we will always use the same Handle
  210. Table slot when accessing a given Turbo file, though several Turbo files may
  211. share the same Handle Table slot.  The scheme works as follows: After a Reset
  212. or Rewrite that opens a Turbo file, Turbo stores the file handle as an integer
  213. value in the first two bytes of the FIB that Turbo uses to represent the file.
  214. In truth, the high-order byte of this handle will always be zero, because
  215. (currently) the only handle numbers that DOS supports are 0-19.  Thus, when we
  216. are not using an "extended" file, we free up its Handle Table slot by copying
  217. the DCB number for that file into the second byte (the high-order byte) of the
  218. handle stored in the FIB.  When we want to access the file, we look again at
  219. the first two bytes of the FIB.  We copy the DCB number (stored in the second
  220. byte of the FIB) into the appropriate Handle Table slot (stored in the first
  221. byte of the FIB), zero out the second byte of the FIB (the high-order byte of
  222. the handle), and then invoke the desired Turbo file access routine(s).  When we
  223. are through invoking those routines, we reverse the process, freeing up that
  224. Handle Table slot again.  Since a given "extended" file is always accessed via
  225. the same Handle Table slot, Turbo's Open Handle List works correctly, and the
  226. spurious I/O error F3 is averted.
  227.  
  228. The OpenExtend, UnExtend, and ReExtend routines below use this technique.
  229. OpenExtend(f) is used on a previously-opened file, "f."  It moves f's DCB
  230. number into f's FIB, and stores an FFH into f's slot in the Handle Table.
  231. UnExtend(f) copies the current DCB number (if any) in f's slot of the Handle
  232. Table to a safe place, copies the DCB number of "f" to that slot, and
  233. zeroes-out the DCB number in the FIB, in preparation for its use by Turbo/DOS.
  234. ReExtend(f) moves f's DCB number back into the FIB, and restores the previous
  235. value (if any) of that slot in the Handle Table.
  236.  
  237. To obtain the address of the Handle Table, which is at offset 18H in the PSP,
  238. the program needs to find the address of its PSP.  Normally, this is very easy:
  239. when DOS loads a .COM file, the address of the PSP is just CS:0000.  Using
  240. CS:0000 in this manner would be viable as long as we compile the program to a
  241. .COM file and execute the .COM file.  However, if we run the program in memory,
  242. from the Turbo menu, Turbo makes a copy of its own PSP for use by the program.
  243. DOS still thinks of Turbo's PSP as being the "official" PSP for the running
  244. program, since DOS did not "see" Turbo invoke the program.  Hence, CS:0000 is
  245. not the valid PSP address when the program is running in memory, since that is
  246. the address of the program's "fake" PSP rather than Turbo's PSP.
  247.  
  248. To allow the program to work correctly both when running in memory and when run
  249. as a .COM file, we use the DOS function call 62H, "Get Program Segment Prefix
  250. Address (PSP)."  This function call is available in DOS 3.0 and higher.  There
  251. is an identical function call in DOS 2.0/2.1, but its function number is 51H,
  252. and it is not documented.  Function 51H is also available in DOS 3.0/3.1.
  253. However, for upward-compatibility reasons with future versions of DOS, we will
  254. use the undocumented 51H function with DOS 2.0/2.1 (since we know 51H is
  255. available in those versions of DOS), and use 62H for DOS 3.0 and higher (since
  256. 62H is a documented function).  There is no such function call in DOS 1.0/1.1,
  257. but the technique below will not work with those early versions of DOS anyway,
  258. since they did not provide file handles.  To decide whether to use function 51H
  259. or 62H, we call DOS function 30H, "Get DOS Version Number," to determine which
  260. version of DOS is running.  This strategy for obtaining the Handle Table
  261. address is implemented in the GetHandleTableAddr function, below, which gets
  262. called only once (the first time that OpenExtend is called).
  263.  
  264. Note: The "Extend" technique presented here will not interfere with overlays in
  265. your program (since it only commandeers a Handle Table slot temporarily),
  266. provided that your program leaves at least one DCB available for use by the
  267. Turbo run-time library to read in overlay files.
  268.  
  269. Many thanks to Bela Lubkin (CompuServe 73047,1112) for masterminding this idea,
  270. to Kim Kokkonen (CompuServe 72457,2131) for helping me debug it, and to Scott
  271. Bussinger (CompuServe 72247,2671), who discovered and fixed the insidious
  272. interaction with Turbo's Open Handle List.  For more discussion of Handle
  273. Tables and the implementation of DOS redirection, please see Stan Mitchell,
  274. "Command Line Redirection," PC Tech Journal, January 1986, Page 44.
  275.  
  276. Change Log:
  277.  
  278.   Version 1.0: Original release.
  279.  
  280.   Version 1.1: Changed to invoke a DOS function to get the PSP address, so that
  281.                programs using this technique can be run in memory under Turbo.
  282.  
  283.   Version 1.2: Replaced some outdated comments.
  284.  
  285.   Version 1.3: Changed example program so that it erases the files it creates.
  286.  
  287.   Version 1.4: Added a caveat about compiling programs that use EXTEND.PAS.
  288.  
  289.   Version 1.5: Fixed the interaction with Turbo's Open Handle List, which
  290.                previously causes spurious F3 I/O errors.  Added a warning that
  291.                EXTEND disables the Turbo/DOS automatic file-close feature, and
  292.                how to work around it.
  293.  
  294. ******************************************************************************)
  295.  
  296.  
  297. {$F252}
  298.  
  299. const
  300.   LastHandle = 19;    {Highest-numbered handle}
  301.   UnusedHandle = $FF; {DcbTable entry that denotes an unused handle}
  302. type
  303.   HandleTable = array[0..LastHandle] of Byte;
  304.   HandleTablePtr = ^HandleTable;
  305. const
  306.   TablePtrOk: Boolean = false; {"True" iff TablePtr is initialized}
  307. var
  308.   TablePtr: HandleTablePtr; {Points to Handle Table for this process}
  309.   SaveDcb: Byte; {Temporary variable for a DCB number during a function call}
  310.  
  311.  
  312. {Internal routine.  Returns the address of the Handle Table, which is at offset
  313.  18H in the PSP.}
  314.  
  315. function GetHandleTableAddr: HandleTablePtr;
  316. var
  317.   regs: record
  318.           case Integer of
  319.             1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
  320.             2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
  321.           end;
  322. begin
  323.   regs.AH := $30;
  324.   MsDos(regs);         {Get DOS version number}
  325.   case regs.AL of
  326.     {If running under a DOS earlier than 2.0, force a run-time error}
  327.     0: regs.AL := regs.AL div 0;
  328.     2: regs.AH := $51; {Undocumented, but works with DOS 2.0/2.1 (and 3.X)}
  329.   else regs.AH := $62  {Works with DOS 3.0 and higher}
  330.   end;
  331.   MsDos(regs);         {Get PSP address}
  332.   GetHandleTableAddr := Ptr(regs.BX, $18)
  333. end {GetHandleTableAddr};
  334.  
  335.  
  336. {Causes "f" to become an "extended" file; i.e., to remain open without using up
  337.  any file handles.  The parameter "f" must be any Turbo file; e.g., a File, a
  338.  File of Byte, a File of Foo, Text, etc.  This routine should be called
  339.  immediately after the Reset or Rewrite that is initially used to open "f."
  340.  After "f" has become extended, it cannot be used by Turbo's built-in file
  341.  routines until it has been unextended using UnExtend.}
  342.  
  343. procedure OpenExtend (var f);
  344. var
  345.   fib: record handle, dcbnum: Byte end absolute f;
  346. begin
  347.   if not TablePtrOk then
  348.     begin
  349.       TablePtr := GetHandleTableAddr;
  350.       TablePtrOk := true
  351.     end;
  352.  
  353.   {If the "dcbnum" byte is already in use, a futuristic DOS must be running
  354.    that allows more than 255 handles in a single process.  In this case, force
  355.    a run-time error.}
  356.   if fib.dcbnum <> 0 then fib.dcbnum := fib.dcbnum div 0;
  357.  
  358.   fib.dcbnum := TablePtr^[fib.handle];
  359.   TablePtr^[fib.handle] := UnusedHandle
  360. end {OpenExtend};
  361.  
  362.  
  363. {Unextends the extended file "f," so that it can be used by any of Turbo's
  364.  built-in file routines.  Note that "f" must have been converted to an extended
  365.  file by OpenExtend before invoking UnExtend(f).  After calling UnExtend, and
  366.  then invoking the Turbo file function on "f," ReExtend(f) should be invoked
  367.  immediately to re-extend "f" and restore the DOS file state information.}
  368.  
  369. procedure UnExtend (var f);
  370. var
  371.   fib: record handle, dcbnum: Byte end absolute f;
  372. begin
  373.   SaveDcb := TablePtr^[fib.handle];
  374.   TablePtr^[fib.handle] := fib.dcbnum;
  375.   fib.dcbnum := 0
  376. end {UnExtend};
  377.  
  378.  
  379. {Re-extends "f" into an extended file.  Note that "f" must have been converted
  380.  to an extended file by OpenExtend, and then unextended using UnExtend, before
  381.  "f" can re-extended using ReExtend.  ReExtend(f) should be invoked immediately
  382.  after any normal Turbo file function call using "f."}
  383.  
  384. procedure ReExtend (var f);
  385. var
  386.   fib: record handle, dcbnum: Byte end absolute f;
  387. begin
  388.   fib.dcbnum := TablePtr^[fib.handle];
  389.   TablePtr^[fib.handle] := SaveDcb
  390. end {ReExtend};
  391.  
  392.  
  393.  
  394. {Example program -- remove the "(*" line below to enable the program (a Bela
  395.  Lubkin trick).  This program opens as many Text files as it can, until DOS
  396.  runs out of room in its DCB Table.  It then reports how many files were
  397.  successfully opened, writes a line to each of them, then closes and erases
  398.  each of them.  Note: The value of the "FILES=nnn" parameter in the CONFIG.SYS
  399.  file must be set to an appropriate value (see above).  If you change the
  400.  "FILES=nnn" value, be sure to reboot before running this program.
  401.  
  402.  This program takes a while to run, due to the heavy disk I/O, so running it on
  403.  a hard disk (or, even better, a RAM disk) is recommended.  Make sure that you
  404.  are running the program in a subdirectory, so that you don't run up against
  405.  the DOS limit on the number of allowable files in the root directory of a
  406.  drive.}
  407.  
  408. (*
  409. const
  410.   MaxCount = 255;
  411. var
  412.   num: string[6];
  413.   f: array[1..MaxCount] of Text;
  414.   i, count: Integer;
  415.   result: Byte;
  416. begin
  417.   writeln('Opening files...');
  418.   i := 0;
  419.   repeat
  420.     i := i + 1;
  421.     Str(i, num);
  422.     Assign(f[i], 'junk' + num + '.txt');
  423.     {$I-} Rewrite(f[i]); {$I+}
  424.     result := IOResult;
  425.     if result = 0 then OpenExtend(f[i])
  426.   until result <> 0;
  427.   count := i - 1;
  428.   writeln('Successfully opened ', count, ' files at the same time.  ',
  429.           'Writing to each file...');
  430.   for i := 1 to count do
  431.     begin
  432.       UnExtend(f[i]);
  433.       writeln(f[i], 'This is a test');
  434.       ReExtend(f[i])
  435.     end;
  436.   writeln('Closing and erasing each file...');
  437.   for i := 1 to count do
  438.     begin
  439.       UnExtend(f[i]);
  440.       Close(f[i]);
  441.       Erase(f[i]);
  442.       ReExtend(f[i])
  443.     end;
  444.   writeln('Done.')
  445. end.
  446. (**)
  447.